feat: arch intrinsic feature discovery pattern#8750
Conversation
c452fcb to
4e917a4
Compare
Merging this PR will improve performance by 16.52%
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ⚡ | Simulation | new_bp_prim_test_between[i32, 32768] |
155.4 µs | 124 µs | +25.29% |
| ⚡ | Simulation | new_bp_prim_test_between[i16, 32768] |
151.3 µs | 122.2 µs | +23.77% |
| ⚡ | Simulation | new_alp_prim_test_between[f32, 32768] |
170 µs | 138.6 µs | +22.67% |
| ⚡ | Simulation | new_bp_prim_test_between[i32, 16384] |
96.2 µs | 80.2 µs | +19.94% |
| ⚡ | Simulation | new_bp_prim_test_between[i16, 16384] |
92.8 µs | 78 µs | +18.85% |
| ⚡ | Simulation | new_bp_prim_test_between[i64, 32768] |
163.3 µs | 138.3 µs | +18.09% |
| ⚡ | Simulation | new_alp_prim_test_between[f32, 16384] |
106.8 µs | 90.9 µs | +17.56% |
| ⚡ | Simulation | new_raw_prim_test_between[f32, 32768] |
121.5 µs | 103.9 µs | +17.01% |
| ⚡ | Simulation | new_alp_prim_test_between[f64, 32768] |
178 µs | 153.1 µs | +16.26% |
| ⚡ | Simulation | new_raw_prim_test_between[i32, 32768] |
114.7 µs | 100.5 µs | +14.16% |
| ⚡ | Simulation | new_raw_prim_test_between[u32, 32768] |
116.8 µs | 102.3 µs | +14.14% |
| ⚡ | Simulation | new_bp_prim_test_between[i64, 16384] |
102 µs | 89.4 µs | +14.1% |
| ⚡ | Simulation | new_raw_prim_test_between[f32, 16384] |
75.2 µs | 66.2 µs | +13.5% |
| ⚡ | Simulation | new_alp_prim_test_between[f64, 16384] |
113.8 µs | 101.1 µs | +12.6% |
| ⚡ | Simulation | compress[(100000, 4)] |
1.8 ms | 1.6 ms | +11.96% |
| ⚡ | Simulation | new_raw_prim_test_between[u32, 16384] |
72.9 µs | 65.6 µs | +11.18% |
| ⚡ | Simulation | new_raw_prim_test_between[i32, 16384] |
71.8 µs | 64.6 µs | +11.11% |
| 🆕 | Simulation | collect_bool_u32_gt[1024] |
N/A | 6.4 µs | N/A |
| 🆕 | Simulation | collect_bool_u32_gt[65536] |
N/A | 205.5 µs | N/A |
| 🆕 | Simulation | from_bool_slice[1024] |
N/A | 2.2 µs | N/A |
| ... | ... | ... | ... | ... | ... |
ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.
Tip
Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.
Comparing claude/vortex-aarch-pattern-abstraction-s3javb (3f1fea8) with develop (f742e8a)2
Footnotes
-
52 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
-
No successful run was found on
develop(8f75dbe) during the generation of this report, so f742e8a was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩
e0859ef to
db223d1
Compare
db223d1 to
d67d634
Compare
Polar Signals Profiling ResultsLatest Run
Previous Runs (1)
Powered by Polar Signals Cloud |
Benchmarks: FineWeb NVMeVerdict: No clear signal (environment too noisy confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (0.993x ➖, 0↑ 0↓)
datafusion / vortex-compact (0.986x ➖, 1↑ 0↓)
datafusion / parquet (0.934x ➖, 3↑ 0↓)
duckdb / vortex-file-compressed (1.007x ➖, 0↑ 0↓)
duckdb / vortex-compact (1.000x ➖, 0↑ 0↓)
duckdb / parquet (0.995x ➖, 1↑ 0↓)
No file size changes detected. |
Benchmarks: PolarSignals ProfilingVortex (geomean): 1.033x ➖ How to read Verdict and Engines
datafusion / vortex-file-compressed (1.033x ➖, 0↑ 2↓)
No file size changes detected. |
Benchmarks: TPC-H SF=1 on NVMEVerdict: No clear signal (environment too noisy confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (0.979x ➖, 0↑ 0↓)
datafusion / vortex-compact (0.975x ➖, 0↑ 0↓)
datafusion / parquet (0.985x ➖, 2↑ 1↓)
datafusion / arrow (0.971x ➖, 2↑ 1↓)
duckdb / vortex-file-compressed (0.978x ➖, 0↑ 0↓)
duckdb / vortex-compact (0.986x ➖, 0↑ 0↓)
duckdb / parquet (0.990x ➖, 3↑ 3↓)
duckdb / duckdb (0.982x ➖, 0↑ 0↓)
No file size changes detected. |
Benchmarks: TPC-DS SF=1 on NVMEVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.015x ➖, 1↑ 1↓)
datafusion / vortex-compact (1.012x ➖, 0↑ 1↓)
datafusion / parquet (0.996x ➖, 5↑ 2↓)
duckdb / vortex-file-compressed (1.016x ➖, 1↑ 3↓)
duckdb / vortex-compact (1.015x ➖, 1↑ 1↓)
duckdb / parquet (1.011x ➖, 1↑ 2↓)
duckdb / duckdb (1.025x ➖, 1↑ 6↓)
No file size changes detected. |
Benchmarks: FineWeb S3Verdict: No clear signal (environment too noisy confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (0.928x ➖, 1↑ 0↓)
datafusion / vortex-compact (1.006x ➖, 0↑ 0↓)
datafusion / parquet (1.131x ➖, 0↑ 2↓)
duckdb / vortex-file-compressed (0.968x ➖, 0↑ 0↓)
duckdb / vortex-compact (0.998x ➖, 0↑ 0↓)
duckdb / parquet (0.963x ➖, 0↑ 0↓)
|
Benchmarks: Statistical and Population GeneticsVerdict: No clear signal (low confidence) How to read Verdict and Engines
duckdb / vortex-file-compressed (1.001x ➖, 0↑ 0↓)
duckdb / vortex-compact (1.000x ➖, 0↑ 0↓)
duckdb / parquet (1.008x ➖, 0↑ 0↓)
No file size changes detected. |
Benchmarks: Clickbench Sorted on NVMEVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (0.999x ➖, 1↑ 1↓)
datafusion / parquet (0.996x ➖, 0↑ 0↓)
duckdb / vortex-file-compressed (0.945x ➖, 2↑ 0↓)
duckdb / parquet (0.981x ➖, 1↑ 0↓)
duckdb / duckdb (1.013x ➖, 0↑ 0↓)
File Size Changes (201 files changed, +0.0% overall, 114↑ 87↓)
Totals:
|
Benchmarks: Random AccessVortex (geomean): 1.136x ❌ How to read Verdict and Engines
unknown / unknown (1.078x ➖, 0↑ 16↓)
|
🚨🚨🚨❌❌❌ SQL BENCHMARK FAILED ❌❌❌🚨🚨🚨Benchmark |
Benchmarks: Clickbench on NVMEVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (0.959x ➖, 3↑ 0↓)
datafusion / parquet (0.936x ➖, 7↑ 0↓)
duckdb / vortex-file-compressed (0.972x ➖, 2↑ 0↓)
duckdb / parquet (0.985x ➖, 1↑ 0↓)
duckdb / duckdb (0.960x ➖, 1↑ 0↓)
File Size Changes (1 files changed, -0.0% overall, 0↑ 1↓)
Totals:
|
Benchmarks: TPC-H SF=1 on S3Verdict: No clear signal (environment too noisy confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.040x ➖, 0↑ 2↓)
datafusion / vortex-compact (1.053x ➖, 0↑ 2↓)
datafusion / parquet (1.063x ➖, 0↑ 2↓)
duckdb / vortex-file-compressed (0.992x ➖, 0↑ 0↓)
duckdb / vortex-compact (1.003x ➖, 0↑ 0↓)
duckdb / parquet (1.034x ➖, 0↑ 0↓)
|
Benchmarks: Appian on NVMEVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (0.942x ➖, 2↑ 0↓)
datafusion / parquet (1.019x ➖, 0↑ 0↓)
duckdb / vortex-file-compressed (0.999x ➖, 0↑ 0↓)
duckdb / parquet (0.945x ➖, 2↑ 0↓)
duckdb / duckdb (0.991x ➖, 0↑ 0↓)
File Size Changes (1 files changed, -0.0% overall, 0↑ 1↓)
Totals:
|
Benchmarks: TPC-H SF=10 on S3Verdict: No clear signal (environment too noisy confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (0.986x ➖, 1↑ 2↓)
datafusion / vortex-compact (1.056x ➖, 0↑ 2↓)
datafusion / parquet (1.022x ➖, 0↑ 0↓)
duckdb / vortex-file-compressed (0.950x ➖, 0↑ 0↓)
duckdb / vortex-compact (0.999x ➖, 0↑ 0↓)
duckdb / parquet (0.911x ➖, 0↑ 0↓)
|
Benchmarks: CompressionVortex (geomean): 0.999x ➖ How to read Verdict and Engines
unknown / unknown (0.984x ➖, 9↑ 1↓)
|
| use vortex_buffer::CpuKernel; | ||
|
|
||
| /// Signature shared by all 1024-bit transpose kernels. | ||
| type TransposeKernel = unsafe fn(&[u8; 128], &mut [u8; 128]); |
There was a problem hiding this comment.
We need to rebase, this is no longer here
There was a problem hiding this comment.
Rebased onto the latest develop (6b27c45) and force-pushed. The rebase was clean — no upstream commits touched the files in this PR, and bit_transpose/mod.rs is unchanged on develop, so the diff here is unaffected.
Generated by Claude Code
There was a problem hiding this comment.
oh, I forgot that this is in my pr, sorry claude
Hot kernels re-ran cached CPU-feature checks on every call: each is_x86_feature_detected! is an atomic load + bit test per call, and sites like select_in_chunk chained three of them per 64-byte chunk. aarch64 paths were separate cfg blocks at every call site. Add vortex_buffer::CpuKernel<F>, a LazyLock-like slot for function pointers: the selector passed to CpuKernel::new runs once on the first get(), and every later call is a relaxed atomic load, a never-taken predicted branch, and an indirect call. No macros; the two transmute_copy calls between F and the atomic pointer are contained in the type, whose constructor statically asserts F is pointer-sized. Selectors model both dispatch dimensions in plain code: one cfg(target_arch) block per architecture that returns early (runtime feature probes as an if-chain inside), then the portable default as the plain tail - no cfg(not(any(...))) negation blocks anywhere. An arm that returns unconditionally (aarch64 NEON needs no probe) makes the tail unreachable there, silenced by an allow(unreachable_code) scoped to just the tail block. Kernel types are unsafe fn pointers so #[target_feature] kernels coerce directly by name with a single narrow unsafe call per site. Tiny inputs are gated before the dispatch so they keep the old fully inlined code path: count_ones_aligned calls the scalar kernel directly below 32 bytes (where SIMD never engaged anyway), and scan_chunks calls the per-architecture unconditional kernel directly for two chunks or fewer. Without these gates the dispatch indirection dominates sub-SIMD-threshold workloads: CodSpeed flagged true_count[128] (~-30%) and rank_single[(1024, 0.1)]; cachegrind measured the rank regression at +27 instructions per call and parity after the gates (37.70M vs 37.71M baseline instructions for 100k rank calls). Converted sites: fastlanes transpose_bits/untranspose_bits, vortex-buffer scan_chunks/select_in_chunk/select_in_word/count_ones_aligned, vortex-array filter_bitbuffer_by_mask, and vortex-mask intersect_bit_buffers_dispatch/intersect_rank_indices_dispatch. Intentionally left: primitive take (already one-time via LazyLock dyn kernel), intersect_mask_driven_dispatch (generic over an iterator type, so its instantiations cannot share one fn-pointer slot), and the compile-time IS_CONST_LANE_WIDTH constant. The count_ones any-length wrappers carry #[target_feature] so the SIMD kernels inline into them, avoiding an extra call level, and the vortex_bitbuffer and rank bench mains pre-resolve the kernel slots (matching the existing CPUID pre-warm precedent) so no single-shot benchmark measurement includes first-call selection. benches/cpu_dispatch.rs compares dispatch overhead per call against the same #[inline(never)] kernel. Fastest-of-3 per 1024 calls: direct 2.44us, CpuKernel 2.44us (indistinguishable, confirmed by disassembly: load + fused null test + indirect jmp), LazyLock<fn> 3.04us, and three cached feature checks per call 4.54us. All touched crates also cargo check clean on aarch64-unknown-linux-gnu. Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DUFxXZLv4xxEqEYcoGWhfJ
d67d634 to
841d0e4
Compare
…h-pattern-abstraction-s3javb Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk> # Conflicts: # vortex-buffer/Cargo.toml
Benchmarks: Vortex queriesVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.000x ➖, 0↑ 0↓)
datafusion / parquet (0.937x ➖, 0↑ 0↓)
duckdb / vortex-file-compressed (0.952x ➖, 0↑ 0↓)
duckdb / parquet (0.980x ➖, 0↑ 0↓)
No file size changes detected. |
Currently we have a few places where we have arch/feature specific kernels, they current use slower feature detection for each call.
This PR add a cache for the kernel to use and uses this pattern in the codebase. I think we would later want to register different kernels in the vortex session instead of this, but that can wait.